package me.leon.ade

import java.io.File
import java.io.RandomAccessFile
import java.net.HttpURLConnection
import java.net.URL
import kotlin.math.min
import me.leon.ade.NetHelper.ILLEGAL_FILE_NAME_PATTERN

/** 多线程下载 和 断点续传 多文件实时存储进度 */
class MultiThreadDownloader(
    val path: String =
        "http://d0.ananas.chaoxing.com/download/e9c3d6ee0914e4555886d907d4e684b4?" +
            "at_=1613979050848&ak_=aeb2d0e7ff4cca655033b541c3f8bc22&ad" +
            "_=ca01c86bc3fe8e630fb07d1966bf7e5e&fn=%E3%80%8A%E6%95%B" +
            "0%E6%8D%AE%E5%8E%8B%E7%BC%A9%E5%AF%BC%E8%AE%BA%EF%BC%88%E" +
            "7%AC%AC4%E7%89%88%EF%BC%89%E3%80%8B.%28%E8%B4%BE%E6%B4%AA%E5%" +
            "B3%B0%29.%5BPDF%5D%40jb51.net",
    val targetDir: String = "/multi/files/",
    val threadCount: Int = Runtime.getRuntime().availableProcessors(),
    val headers: MutableMap<String, Any> = mutableMapOf(),
    val onCompleted: (String) -> Unit = {},
    val onError: (String) -> Unit = {}
) {

    private var completedCount = 0
    private var filePath = ""
    var md5 = ""
    var fileName = ""
    var fileSize = 0L

    /** 下载文件 */
    @Throws(Exception::class)
    fun download() {
        if (!File(targetDir).exists()) File(targetDir).mkdirs()
        // 不下载文件 获取文件大小  文件名称
        URL(path).openConnection().safeAs<HttpURLConnection>()?.run {
            requestMethod = "HEAD"
            connectTimeout = 10000
            setRequestProperty("user-agent", NetHelper.COMMON_UA)
            headers.forEach { setRequestProperty(it.key, it.value.toString()) }
            if (responseCode == 200) {
                if (!getHeaderField("etag").isNullOrEmpty()) {
                    md5 = getHeaderField("etag").removeSurrounding("\"")
                }
                fileName =
                    NetHelper.getNetFileName(this, path)
                        .also { println(it) }
                        .replace(ILLEGAL_FILE_NAME_PATTERN.toRegex(), "_")
                println(ChareDect.getEncoding(fileName))
                println("$md5--$fileName")
                // 在本地创建一个与资源同样大小的文件来占位
                RandomAccessFile(
                        File(targetDir, fileName).also { filePath = it.absolutePath },
                        "rw"
                    )
                    .apply { setLength(contentLengthLong) }
                    .use {
                        // 将下载任务分配给每个线程
                        //                    println(contentLengthLong)
                        contentLengthLong
                            .also { fileSize = it }
                            .slice(threadCount)
                            .forEachIndexed { index, longRange ->
                                DownloadThread(index, longRange.first, longRange.last)
                                    .start() // 开启线程下载
                            }
                    }
            }
        }
    }

    private fun deleteConfigFiles() {
        for (id in 0..threadCount) File(targetDir, "${fileName.hashCode()}_$id.cfg").delete()
    }

    // 下载线程
    private inner class DownloadThread(
        val threadId: Int,
        var startIndex: Long,
        val endIndex: Long
    ) : Thread() {
        val showProgressInterval = 500L
        var lastShowTime = 0L
        override fun run() {

            println("thread $threadId start downloading $startIndex  $endIndex  $fileSize")
            try {
                // 分段请求网络连接,分段将文件保存到本地.
                val configFile = File(targetDir, "${fileName.hashCode()}_$threadId.cfg")
                val configAccessFile: RandomAccessFile
                if (configFile.exists()) {
                    configAccessFile = RandomAccessFile(configFile, "rwd")
                    println("配置文件存在")
                    val startindexStr = configAccessFile.readLine()
                    startIndex = if (startindexStr.isNullOrEmpty()) 0L else startindexStr.toLong()
                } else {
                    configAccessFile = RandomAccessFile(configFile, "rwd")
                }
                startIndex = min(startIndex, endIndex)
                URL(path).openConnection().safeAs<HttpURLConnection>()?.run {
                    requestMethod = "GET"
                    connectTimeout = 10000
                    setRequestProperty("user-agent", NetHelper.COMMON_UA)
                    // 设置分段下载的头信息。  Range:做分段数据请求用的。格式: Range bytes=0-1024  或者 bytes:0-1024
                    setRequestProperty("Range", "bytes=$startIndex-$endIndex")
                    headers.forEach { setRequestProperty(it.key, it.value.toString()) }
                    println("thread_$threadId from $startIndex to  $endIndex  $fileSize")
                    if (responseCode == 206) { // 200：请求全部资源成功， 206代表部分资源请求成功
                        val randomAccessFile = RandomAccessFile(File(targetDir, fileName), "rw")
                        randomAccessFile.seek(startIndex) // 文件写入的开始位置.
                        val buffer = ByteArray(8 * 1024)
                        var length = -1
                        var total = 0L
                        configAccessFile.use {
                            randomAccessFile.use { raf ->
                                inputStream.use { input ->
                                    while (input.read(buffer).also { length = it } > 0) {
                                        raf.write(buffer, 0, length)
                                        total += length
                                        showProgress(total)
                                        configAccessFile.seek(0)
                                        configAccessFile.write(
                                            (startIndex + total).toString().toByteArray()
                                        )
                                    }
                                }
                            }
                        }
                        println("线程 $threadId 下载完毕")
                        completedCount++
                        if (completedCount == threadCount) {
                            deleteConfigFiles()
                            onCompleted(filePath)
                        }
                    } else {
                        println("code $responseCode. 服务器不支持多线程下载")
                        onError("code $responseCode. 服务器不支持多线程下载")
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }

        private fun showProgress(total: Long) {
            if (System.currentTimeMillis() - lastShowTime < showProgressInterval) {
                return
            }
            lastShowTime = System.currentTimeMillis()
            println(
                "thread_$threadId :  ${total}/${endIndex - startIndex}  (${
                    String.format(
                        "%.2f%%",
                        100 * total.toFloat() / (endIndex - startIndex)
                    )
                })"
            )
        }
    }

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            var isComplete = false
            MultiThreadDownloader(
                    onCompleted = {
                        println(
                            "filePath: $it  md5: ${
                        it.fileMd5().also { println(it == "C8773345B4783FE4AC676C646B1239A9") }
                    } "
                        )
                        isComplete = true
                    }
                )
                .download()

            while (!isComplete) {
                // dead loop
            }
        }
    }
}
